package fileHandling;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;

import settings.Config;
import settings.GraphicSettings;

import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.Spatial.CullHint;
import com.jme.util.CloneImportExport;
import com.jme.util.export.Savable;
import com.jme.util.export.binary.BinaryImporter;
import com.jme.util.resource.ResourceLocatorTool;
import com.jmex.model.converters.FormatConverter;
import com.jmex.model.converters.ObjToJme;

/**
 * Provides static methods to load models.
 * @author Wasserleiche
 */
public class ModelImporter {
	
	/** This {@link HashMap} is used to store {@link CloneImportExport}-objects of loaded objects to faster load 
	 * those objects again.*/
	protected static HashMap<String, ClonedModel> loadedObjects = new HashMap<String, ClonedModel>();
	
	public static void clearModelCache() {
		for(ClonedModel c : loadedObjects.values()) {
			c.cleanParent();
		}
		loadedObjects.clear();
	}
	
	/**
	 * Checks weather the given key is known.
	 * @param key The key to be looked up.
	 * @return true, if the key has a corresponding object in the loadedObjects-{@link HashMap}. false, else.
	 */
	protected static boolean objectIsLoaded(String key) {
		if(!Config.get().getCloneLoading()) return false;
		return loadedObjects.containsKey(key);
	}
	
	/**
	 * Returns the loaded model of the given model-path. This method shall only be called 
	 * if there is a model stored in the {@link HashMap}!.
	 * @param modelPath The path of the model to be retrieved.
	 * @return A {@link Node} containing the model.
	 */
	protected static Node getLoadedNode(String modelPath) {
		assert(modelPath != null && objectIsLoaded(modelPath));
		return loadedObjects.get(modelPath).loadClone();
	}
	
	/**
	 * Creates a new {@link CloneImportExport}-object out of the given model-{@link Node} 
	 * and stores it in the loadedModels-{@link HashMap} with the model-path.
	 * @param modelPath The model-path that will become the key in the {@link HashMap}.
	 * @param model The {@link Node} that has to be saved in a {@link CloneImportExport}-object.
	 */
	protected static void putLoadedNode(String modelPath, Node model) {
		assert(modelPath != null && model != null);
		loadedObjects.put(modelPath, new ClonedModel(model));
	}
	
	public static Node getModel(String modelPath, String quality) {
		String path = modelPath + quality + "/";
		URL modelFile = getURL(path + "model.obj", ResourceLocatorTool.TYPE_MODEL);
		if(modelFile == null) return null;
		URL modelFolder = getURL(path, ResourceLocatorTool.TYPE_SHADER);
		if(modelFolder == null) return null;
		
		String qual = GraphicSettings.get().getTextureQualityString().toLowerCase();
		URL textureFolder = getURL(TextureLoader.QUAL_PATH + qual + "/", ResourceLocatorTool.TYPE_TEXTURE);
		FormatConverter converter = new ObjToJme();
		converter.setProperty("mtllib", modelFolder);
		converter.setProperty("texdir", textureFolder);
		ByteArrayOutputStream bo = new ByteArrayOutputStream();
		Node model = null;
		ByteArrayInputStream bai = null;
		try {
			converter.convert(modelFile.openStream(), bo);
			bai = new ByteArrayInputStream(bo.toByteArray());
			Savable savable = BinaryImporter.getInstance().load(bai);
			
			if(savable instanceof Node) model = (Node)savable;
			else {
				model = new Node();
				model.attachChild((Spatial)savable);
			}
		} catch(Exception e) {
			e.printStackTrace();
			return null;
		} finally {
			try {
				bo.close();
				if(bai != null) bai.close();
			} catch(Exception e) { e.printStackTrace(); }
		}
		
		model.setCullHint(CullHint.Dynamic);
		putLoadedNode(path, model);
		
		model.setName(path);
		
		return model;
	}
	
	public static Node getModelOfQuality(String modelPath, String quality) {
		String path = modelPath + quality + "/";
		if(objectIsLoaded(path)) {
			Node node = getLoadedNode(path);
			if(node != null) return node;
		}
		return getModel(modelPath, quality);
    }
	
	/**
	 * Creates a new model-Node with the given model-path.
	 * @param modelPath The relative path to the model.
	 * @return A new {@link Node} that contains the model. 
	 * 			null, if any error occurs while loading the model.
	 */
	public static Node getModel(String modelPath) {
		String quality = GraphicSettings.get().getGeometryQuality();
		if(!Config.get().useLOD()) quality = "low";
		String path = modelPath + quality + "/";
		if(objectIsLoaded(path)) {
			Node node = getLoadedNode(path);
			if(node != null) return node;
		}
		return getModel(modelPath, quality);
    }
	
	public static URL getURL(String urlPath, String type) {
		URL modelFile = ResourceLocatorTool.locateResource(type, urlPath);
		if(modelFile == null) {
			try { modelFile = new URL("file:" + urlPath); }
			catch(MalformedURLException e) {
				e.printStackTrace();
				return null;
			}
		}
		return modelFile;
	}
	
	public static String nextLevel(String level, boolean lower) {
		if(level.equals("high") && lower) return "medium";
		if(level.equals("medium")) {
			if(lower) return "low";
			return "high";
		}
		if(level.equals("low")) {
			if(lower) return "lod1";
			return "medium";
		}
		if(level.equals("lod1")) {
			if(lower) return "lod2";
			return "low";
		}
		if(level.equals("lod2")) {
			if(lower) return "lod3";
			return "lod1";
		}
		if(level.equals("lod3") && !lower) return "lod2";
		return null;
	}
}